﻿/*

The MIT License

Copyright (c) 2010 Cartesian Analytics, Inc. 

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

using System;

namespace Pipra.Math.Geometry
{
    /// <summary>
    /// A straight line segment of minimal length between two points.
    /// </summary>
    public class Segment2 :
        ISegment2<double>,
        IEquatable<Segment2>,
        IIntersectable<Segment2>,
        IHasDistance<double,Segment2>,
        IIntersectable<Ray2>,
        IContainable<Mbr>,
        ICloneable
    {

        public static bool operator ==(Segment2 a, Segment2 b)
        {
            return ReferenceEquals(a, b) || (!ReferenceEquals(null, a) && a.Equals(b));
        }

        public static bool operator !=(Segment2 a, Segment2 b)
        {
            return !ReferenceEquals(a, b) && (ReferenceEquals(null, a) || !a.Equals(b));
        }

        /// <summary>
        /// Determines the distance between a point <paramref name="p"/> and a line segment defined by <paramref name="a"/> and <paramref name="b"/>.
        /// </summary>
        /// <param name="a">An end point of a segment.</param>
        /// <param name="b">An end point of a segment.</param>
        /// <param name="p">A point.</param>
        /// <returns>Distance from a point to a line segment.</returns>
        public static double Distance(Point2 a, Point2 b, Point2 p)
        {
            Vector2 d, v;
            if (a.CompareTo(b) <= 0)
            {
                d = b.Difference(a);
                v = p.Difference(a);
            }
            else
            {
                d = a.Difference(b);
                v = p.Difference(b);
            }
            double aDot = d.Dot(v);
            if (aDot <= 0)
            {
                return v.GetMagnitude();
            }
            double dMag = d.GetMagnitudeSquared();
            return (
                (aDot >= dMag)
                ? p.Difference((a.CompareTo(b) <= 0) ? b : a).GetMagnitude()
                : System.Math.Sqrt(System.Math.Max(0, v.GetMagnitudeSquared() - ((aDot * aDot) / dMag)))
            );
        }
        /// <summary>
        /// Determines the squared distance between a point <paramref name="p"/> and a line segment defined by <paramref name="a"/> and <paramref name="b"/>.
        /// </summary>
        /// <param name="a">An end point of a segment.</param>
        /// <param name="b">An end point of a segment.</param>
        /// <param name="p">A point.</param>
        /// <returns>Squared distance from a point to a line segment.</returns>
        public static double DistanceSquared(Point2 a, Point2 b, Point2 p)
        {
            Vector2 d, v;
            if (a.CompareTo(b) <= 0)
            {
                d = b.Difference(a);
                v = p.Difference(a);
            }
            else
            {
                d = a.Difference(b);
                v = p.Difference(b);
            }
            double aDot = d.Dot(v);
            if (aDot <= 0)
            {
                return v.GetMagnitudeSquared();
            }
            double dMag = d.GetMagnitudeSquared();
            return (
                (aDot >= dMag)
                ? p.Difference((a.CompareTo(b) <= 0) ? b : a).GetMagnitudeSquared()
                : System.Math.Max(0, v.GetMagnitudeSquared() - ((aDot * aDot) / dMag))
            );
        }
        /// <summary>
        /// Determines the distance between two segments, one defined by <paramref name="a"/> and <paramref name="b"/>, the other defined by <paramref name="c"/> and <paramref name="d"/>.
        /// </summary>
        /// <param name="a">An end point of the first segment.</param>
        /// <param name="b">An end point of the first segment.</param>
        /// <param name="c">An end point of the second segment.</param>
        /// <param name="d">An end point of the second segment.</param>
        /// <returns>The distance between the two segments.</returns>
        public static double Distance(Point2 a, Point2 b, Point2 c, Point2 d)
        {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Determines the squared distance between two segments, one defined by <paramref name="a"/> and <paramref name="b"/>, the other defined by <paramref name="c"/> and <paramref name="d"/>.
        /// </summary>
        /// <param name="a">An end point of the first segment.</param>
        /// <param name="b">An end point of the first segment.</param>
        /// <param name="c">An end point of the second segment.</param>
        /// <param name="d">An end point of the second segment.</param>
        /// <returns>The squared distance between the two segments.</returns>
        public static double DistanceSquared(Point2 a, Point2 b, Point2 c, Point2 d)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Determines two segments intersect, one defined by <paramref name="a"/> and <paramref name="b"/>, the other defined by <paramref name="c"/> and <paramref name="d"/>.
        /// </summary>
        /// <param name="a">An end point of the first segment.</param>
        /// <param name="b">An end point of the first segment.</param>
        /// <param name="c">An end point of the second segment.</param>
        /// <param name="d">An end point of the second segment.</param>
        /// <returns>True when the segments intersect.</returns>
        public static bool Intersects(Point2 a, Point2 b, Point2 c, Point2 d)
        {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Determines the if a point <paramref name="p"/> intersects a line segment defined by <paramref name="a"/> and <paramref name="b"/>.
        /// </summary>
        /// <param name="a">An end point of a segment.</param>
        /// <param name="b">An end point of a segment.</param>
        /// <param name="p">A point.</param>
        /// <returns>True when a point intersects a line segment.</returns>
        public static bool Intersects(Point2 a, Point2 b, Point2 p)
        {
            if (p.Equals(a) || p.Equals(b))
            {
                return true;
            }
            Vector2 d, v;
            if (a.CompareTo(b) <= 0)
            {
                d = b.Difference(a);
                v = p.Difference(a);
            }
            else
            {
                d = a.Difference(b);
                v = p.Difference(b);
            }
            double aDot = d.Dot(v);
            return (
                (aDot <= 0)
                ? (0 == v.X && 0 == v.Y)
                : (
                    !(aDot >= d.GetMagnitudeSquared())
                    && (d.X * v.Y) == (d.Y * v.X)
                )
            );
        }


        /// <summary>
        /// The first point defining an end of the line segment.
        /// </summary>
        public Point2 A;
        /// <summary>
        /// The second point defining and end of the line segment.
        /// </summary>
        public Point2 B;
        /// <summary>
        /// Constructs a segment between two points.
        /// </summary>
        /// <param name="a">A point.</param>
        /// <param name="b">A point.</param>
        public Segment2(Point2 a, Point2 b)
        {
            A = a;
            B = b;
        }
        /// <summary>
        /// Constructs a segment between two points.
        /// </summary>
        /// <param name="a">A point.</param>
        /// <param name="b">A point.</param>
        public Segment2(IPoint2<double> a, IPoint2<double> b)
        {
            A = new Point2(a);
            B = new Point2(b);
        }
        /// <summary>
        /// Constructs a segment from the given point <paramref name="p"/> and following a given vector <paramref name="d"/>.
        /// </summary>
        /// <param name="p">A point.</param>
        /// <param name="d">A direction.</param>
        public Segment2(Point2 p, Vector2 d)
        {
            A = p;
            B = p.Add(d);
        }
        /// <summary>
        /// Constructs a segment from the given point <paramref name="p"/> and following a given vector <paramref name="d"/>.
        /// </summary>
        /// <param name="p">A point.</param>
        /// <param name="d">A direction.</param>
        public Segment2(IPoint2<double> p, IVector2<double> d)
        {
            A = new Point2(p);
            B = A.Add(d);
        }
        /// <summary>
        /// Creates a new segment using the same end points as the given <paramref name="segment"/>.
        /// </summary>
        /// <param name="segment">The segment to copy from.</param>
        public Segment2(Segment2 segment)
			: this(segment.A, segment.B) { }
        /// <summary>
        /// Creates a new segment using the same end points as the given <paramref name="curve"/>.
        /// </summary>
        /// <param name="curve">The curve to copy endpoints from.</param>
        public Segment2(ICurve2<double> curve)
            : this(curve.A, curve.B) { }
        
        /// <summary>
        /// The direction of the line segment from A to B with the same magnitude.
        /// </summary>
        public Vector2 Direction
        {
            get { return B.Difference(A); }
        }

        IPoint2<double> ICurve2<double>.A
        {
            get { return A; }
        }

        IPoint2<double> ICurve2<double>.B
        {
            get { return B; }
        }

        IVector2<double> ICurve2<double>.Direction
        {
            get { return Direction; }
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.
        ///                 </param>
        public bool Equals(Segment2 other)
        {
            return !ReferenceEquals(null,other)
                && (
                    A.Equals(other.A)
                    ? B.Equals(other.B)
                    : (
                        A.Equals(other.B)
                        && B.Equals(other.A)
                    )
                )
            ;
        }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        /// 	<c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="T:System.NullReferenceException">
        /// The <paramref name="obj"/> parameter is null.
        /// </exception>
        public override bool Equals(object obj)
        {
            return null != obj
                && (
                    (obj is Segment2 && Equals(obj as Segment2))
                )
            ;
        }

        /// <summary>
        /// Serves as a hash function for a particular type. 
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override int GetHashCode()
        {
            return (A.CompareTo(B) < 0 ? A.GetHashCode() : B.GetHashCode()) ^ -3939399;
        }
        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return String.Concat('{', A, "},{", B, '}');
        }

        /// <summary>
        /// Calculates the distance between this segment and <paramref name="p"/>
        /// </summary>
        /// <param name="p">The point to calculate distance to.</param>
        /// <returns>The distance.</returns>
        public double Distance(Point2 p)
        {
            return Distance(A, B, p);
        }
        /// <summary>
        /// Calculates the distance between this segment and <paramref name="p"/>
        /// </summary>
        /// <param name="p">The point to calculate distance to.</param>
        /// <returns>The distance.</returns>
        public double Distance(IPoint2<double> p)
        {
            return Distance(new Point2(p));
        }
        /// <summary>
        /// Calculates the squared distance between this segment and <paramref name="p"/>
        /// </summary>
        /// <param name="p">The point to calculate squared distance to.</param>
        /// <returns>The squared distance.</returns>
        public double DistanceSquared(Point2 p)
        {
            return DistanceSquared(A, B, p);
        }
        /// <summary>
        /// Calculates the squared distance between this segment and <paramref name="p"/>
        /// </summary>
        /// <param name="p">The point to calculate squared distance to.</param>
        /// <returns>The squared distance.</returns>
        public double DistanceSquared(IPoint2<double> p)
        {
            return DistanceSquared(new Point2(p));
        }
        /// <summary>
        /// Calculates the distance between this segment and the given <paramref name="other"/>
        /// </summary>
        /// <param name="other">The segment to calculate distance to.</param>
        /// <returns>The distance.</returns>
        public double Distance(ISegment2<double> other)
        {
            return Distance(new Segment2(other.A,other.B));
        }
        /// <summary>
        /// Calculates the distance between this segment and the given <paramref name="other"/>
        /// </summary>
        /// <param name="other">The segment to calculate distance to.</param>
        /// <returns>The distance.</returns>
        public double Distance(Segment2 other)
        {
            return Distance(A, B, other.A, other.B);
        }
        /// <summary>
        /// Calculates the squared distance between this segment and the given <paramref name="other"/>
        /// </summary>
        /// <param name="other">The segment to calculate squared distance to.</param>
        /// <returns>The squared distance.</returns>
        public double DistanceSquared(ISegment2<double> other)
        {
            return DistanceSquared(new Segment2(other));
        }
        /// <summary>
        /// Calculates the squared distance between this segment and the given <paramref name="other"/>
        /// </summary>
        /// <param name="other">The segment to calculate squared distance to.</param>
        /// <returns>The squared distance.</returns>
        public double DistanceSquared(Segment2 other)
        {
            return DistanceSquared(A, B, other.A, other.B);
        }
        /// <summary>
        /// Determines if a <paramref name="point"/> intersects this segment.
        /// </summary>
        /// <param name="point">A point.</param>
        /// <returns>True when intersecting.</returns>
        public bool Intersects(Point2 point)
        {
            return Intersects(A, B, point);
        }
        /// <summary>
        /// Determines if a <paramref name="point"/> intersects this segment.
        /// </summary>
        /// <param name="point">A point.</param>
        /// <returns>True when intersecting.</returns>
        public bool Intersects(IPoint2<double> point)
        {
            return Intersects(new Point2(point));
        }
        /// <summary>
        /// Determines if this segment intersect another <paramref name="segment"/>.
        /// </summary>
        /// <param name="segment">A segment.</param>
        /// <returns>True when another object intersects this object.</returns>
        public bool Intersects(ISegment2<double> segment)
        {
            return Intersects(new Segment2(segment));
        }
        /// <summary>
        /// Determines if this segment intersect another <paramref name="segment"/>.
        /// </summary>
        /// <param name="segment">A segment.</param>
        /// <returns>True when another object intersects this object.</returns>
        public bool Intersects(Segment2 segment)
        {
            return Intersects(A, B, segment.A, segment.B);
        }
        /// <summary>
        /// Calculates the length of this segment.
        /// </summary>
        /// <returns>The length.</returns>
        public double GetMagnitude()
        {
            return A.Distance(B);
        }
        /// <summary>
        /// Calculates the squared length of this segment.
        /// </summary>
        /// <returns>The squared length.</returns>
        public double GetMagnitudeSquared()
        {
            return A.DistanceSquared(B);
        }
        /// <summary>
        /// Creates a new segment with the same end points as this segment.
        /// </summary>
        /// <returns>
        /// A new identical segment.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public Segment2 Clone()
        {
            return new Segment2(this);
        }
        object ICloneable.Clone()
        {
            return Clone();
        }
        /// <summary>
        /// Calculates a minimum bounding rectangle for this segment.
        /// </summary>
        /// <returns>A minimum bounding rectangle.</returns>
        public Mbr GetMbr()
        {
            return new Mbr(A,B);
        }
        IMbr<double> IHasMbr<double>.GetMbr()
        {
            return GetMbr();
        }
        /// <summary>
        /// Calculates the centroid.
        /// </summary>
        /// <returns>A centroid.</returns>
        public Point2 GetCentroid()
        {
            return new Point2(
                (A.X + B.X) / 2.0,
                (A.Y + B.Y) / 2.0
            );
        }
        IPoint2<double> IHasPlanarCentroid<double>.GetCentroid()
        {
            return GetCentroid();
        }
        /// <summary>
        /// Determines if a ray, <paramref name="r"/> intersects this segment.
        /// </summary>
        /// <param name="r">A ray.</param>
        /// <returns>True when intersecting.</returns>
        public bool Intersects(Ray2 r)
        {
            return r.Intersects(this);
        }
        /// <summary>
        /// Determines if the given MBr contains this segment.
        /// </summary>
        /// <param name="mbr">The MBR to test.</param>
        /// <returns>True if this segment is within the given MBR.</returns>
        public bool Within(Mbr mbr)
        {
            return (
                (
                    A.X < B.X
                    ? (mbr.XMin <= A.X) && (B.X <= mbr.XMax)
                    : (mbr.XMin <= B.X) && (A.X <= mbr.XMax)
                )
                &&
                (
                    A.Y < B.Y
                    ? (mbr.YMin <= A.Y) && (B.Y <= mbr.YMax)
                    : (mbr.YMin <= B.Y) && (A.Y <= mbr.YMax)
                )
            );
        }
        /// <summary>
        /// Determines if the given MBr contains this segment.
        /// </summary>
        /// <param name="mbr">The MBR to test.</param>
        /// <returns>True if this segment is within the given MBR.</returns>
        public bool Within(IMbr<double> mbr)
        {
            return Within(new Mbr(mbr));
        }

        bool ISelfIntersectable.IsSelfIntersecting()
        {
            return false;
        }
    }
}
